/*
 * Ganesa Copyright (C) 2008 Wim Crols <twimpiex@gnx.be>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License Version 3
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package be.gnx.ganesa.application.ui;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;

import be.gnx.dukono.coordinate.Projected2DCoordinate;
import be.gnx.dukono.projection.MercatorWithNorthingOffset;
import be.gnx.ganesa.application.controller.DeleteFeatures;
import be.gnx.ganesa.application.event.FeatureChangedEvent;
import be.gnx.ganesa.application.event.FeatureChangedListener;
import be.gnx.ganesa.application.event.FeatureCreatedEvent;
import be.gnx.ganesa.application.event.FeatureCreatedListener;
import be.gnx.ganesa.application.event.LayerCreatedEvent;
import be.gnx.ganesa.application.event.LayerCreatedListener;
import be.gnx.ganesa.application.event.ProjectCreatedEvent;
import be.gnx.ganesa.application.event.ProjectCreatedListener;
import be.gnx.ganesa.application.model.Application;
import be.gnx.ganesa.application.model.Coordinate;
import be.gnx.ganesa.application.model.Cuboid;
import be.gnx.ganesa.application.model.Feature;
import be.gnx.ganesa.application.model.Layer;
import be.gnx.ganesa.application.model.Project;
import be.gnx.ganesa.application.model.feature.Arc;
import be.gnx.ganesa.application.model.feature.Node;
import be.gnx.ganesa.application.model.feature.Point;

public class Map {

  private Canvas mapCanvas;

  private Menu mapMenu;

  private StatusBar statusBar;

  private InfoBar infoBar;

  public Map(Ganesa ganesa, Composite parent) {
    this.statusBar = ganesa.getStatusBar();
    this.infoBar = ganesa.getInfoBar();
    createMap(ganesa, parent);
  }

  public void createMap(final Ganesa ganesa, Composite parent) {
    mapCanvas = new Canvas(parent, SWT.NONE);

    mapMenu = new Menu(parent.getShell(), SWT.POP_UP);
    mapCanvas.setMenu(mapMenu);
    MenuItem deleteItem = new MenuItem(mapMenu, SWT.PUSH);
    deleteItem.setText("Delete");
    MenuItem copyItem = new MenuItem(mapMenu, SWT.PUSH);
    copyItem.setText("Copy");
    MenuItem optionsItem = new MenuItem(mapMenu, SWT.CASCADE);
    optionsItem.setText("Options");
    Menu optionsMenu = new Menu(mapMenu);
    optionsItem.setMenu(optionsMenu);
    MenuItem check = new MenuItem(optionsMenu, SWT.CHECK);
    check.setText("Check");
    
    deleteItem.addSelectionListener(new DeleteFeatures(ganesa));

    final MouseMoveListener mouseMoveListener = new MouseMoveListener() {
      public void mouseMove(MouseEvent mouseEvent) {
        Project project = ganesa.getApplication().getSelectedProject();
        if (project != null) {
          Coordinate mouseCoordinate = mouseCoordinate(project.getZoomBox(), mouseEvent);
          Feature nearestFeature = null;
          if (project.getSelectedLayer() != null) {
            nearestFeature = project.getSelectedLayer().getNearestFeature(mouseCoordinate);
          }
          GC gc = new GC(mapCanvas);
          gc.drawText("x = " + mouseCoordinate.getLongitude() + "; y = " + mouseCoordinate.getLatitude() + "                 ", 0, 0);
          if (nearestFeature != null) {
            gc.drawText("feature identifer = " + nearestFeature.getIdentifier() + "                 ", 0, 20);
            if (nearestFeature instanceof Point) {
              Point point = (Point) nearestFeature;
              gc.drawText("f.x = " + point.getCoordinate().getLongitude() + "; f.y = " + point.getCoordinate().getLatitude() + "                 ", 0, 40);
            }
          }
          gc.dispose();
        }
      }
    };

    final MouseListener mouseListener = new MouseListener() {
      public void mouseDoubleClick(MouseEvent mouseEvent) {
      }

      public void mouseDown(MouseEvent mouseEvent) {
      }

      public void mouseUp(MouseEvent mouseEvent) {
        Project project = ganesa.getApplication().getSelectedProject();
        if (project != null) {
          if (mouseEvent.button == 1) {
            Coordinate mouseCoordinate = mouseCoordinate(project.getZoomBox(), mouseEvent);
            Feature nearestFeature = null;
            if (project.getSelectedLayer() != null) {
              nearestFeature = project.getSelectedLayer().getNearestFeature(mouseCoordinate);
            }
            if (nearestFeature != null) {
              project.getSelectedLayer().addSelectedFeature(nearestFeature);
              GC gc = new GC(mapCanvas);
              drawFeature(ganesa, nearestFeature, true, gc);
              gc.dispose();
            }
          }
        }
      }
    };

    mapCanvas.addMouseTrackListener(new MouseTrackListener() {
      public void mouseEnter(MouseEvent mouseEvent) {
        mapCanvas.addMouseMoveListener(mouseMoveListener);
        mapCanvas.addMouseListener(mouseListener);
      }

      public void mouseExit(MouseEvent mouseEvent) {
        mapCanvas.removeMouseMoveListener(mouseMoveListener);
        mapCanvas.removeMouseListener(mouseListener);
        GC gc = new GC(mapCanvas);
        gc.drawText("                                            ", 0, 0);
        gc.dispose();
      }

      public void mouseHover(MouseEvent mouseEvent) {
      }
    });

    mapCanvas.addPaintListener(new PaintListener() {
      public void paintControl(PaintEvent paintEvent) {
        Project project = ganesa.getApplication().getSelectedProject();
        if (project != null) {
          for (Layer layer : project.getLayers()) {
            for (Arc arc : layer.getArcs()) {
              drawArc(ganesa, arc, false, paintEvent.gc);
            }
            for (Point point : layer.getPoints()) {
              drawPoint(ganesa, point, false, paintEvent.gc);
            }
            for (Node node : layer.getNodes()) {
              drawPoint(ganesa, node, false, paintEvent.gc);
            }
            for (Feature feature : layer.getSelectedFeatures()) {
              drawFeature(ganesa, feature, true, paintEvent.gc);
            }
          }
          if (project.getSelectedLayer() != null) {
            drawBoundingBox(ganesa, project.getSelectedLayer().getBoundingBox(), false, paintEvent.gc);
          }
        }
      }
    });

    ganesa.getApplication().addProjectCreatedListener(new ProjectCreatedListener() {
      @Override
      public void projectCreatedEventhappened(ProjectCreatedEvent event) {
        event.getProject().addLayerCreatedListener(new LayerCreatedListener() {
          @Override
          public void layerCreatedEventhappened(LayerCreatedEvent event) {
            event.getLayer().addDataCreatedListener(new FeatureCreatedListener() {
              @Override
              public void featureCreatedEventhappened(FeatureCreatedEvent event) {
                GC gc = new GC(mapCanvas);
                drawFeature(ganesa, event.getFeature(), false, gc);
                gc.dispose();

                event.getFeature().addFeatureChangedListener(new FeatureChangedListener() {
                  @Override
                  public void featureChangedEventhappened(FeatureChangedEvent event) {
                    GC gc = new GC(mapCanvas);
                    drawFeature(ganesa, event.getFeature(), false, gc);
                    gc.dispose();
                  }
                });
              }
            });
          }
        });
      }
    });

    resize(ganesa.getApplication());
  }

  public void resize(Application application) {
    mapCanvas.setBounds(0/*toolBar.getWidth() */, 0, mapCanvas.getParent().getClientArea().width - infoBar.getWidth(), mapCanvas.getParent().getClientArea().height - statusBar.getHeight());
    Project project = application.getSelectedProject();
    if (project != null) {
      Layer layer = project.getSelectedLayer();
      if (layer != null) {
        MercatorWithNorthingOffset mercator = new MercatorWithNorthingOffset();
        mercator.setOrigin(application.getSelectedProject().getZoomBox().getBottomLeftCoordinate());

        double width = layer.getBoundingBox().getTopRightCoordinate().toProjected2DCoordinate(mercator).getEasting();
        double height = layer.getBoundingBox().getTopRightCoordinate().toProjected2DCoordinate(mercator).getNorthing();
        if ((width > 0) && (height > 0)) {
          double ratio = Math.min(mapCanvas.getClientArea().width / width, mapCanvas.getClientArea().height / height);
          Cuboid zoomBox = new Cuboid(layer.getBoundingBox().getBottomLeftCoordinate(), mapCanvas.getClientArea().height / ratio * layer.getBoundingBox().getHeight() / height, mapCanvas.getClientArea().width / ratio * layer.getBoundingBox().getWidth() / width, 0);
          project.setZoomBox(zoomBox);
        }
      }
    }
  }

  private Coordinate mouseCoordinate(Cuboid zoomBox, MouseEvent mouseEvent) {
    // TODO use the inverse projection to determine the latitude and longitude from cartesian coordinates.
//    System.err.println("mouse x = " + mouseEvent.x);
//    System.err.println("mouse y = " + mouseEvent.y);
//    System.err.println("zoomBox bottom left lat = " + zoomBox.getBottomLeftCoordinate().getLatitude());
//    System.err.println("zoomBox bottom left lon = " + zoomBox.getBottomLeftCoordinate().getLongitude());
//    System.err.println("zoomBox top right lat = " + zoomBox.getTopRightCoordinate().getLatitude());
//    System.err.println("zoomBox top right lon = " + zoomBox.getTopRightCoordinate().getLongitude());
//    System.err.println("zoomBox width = " + zoomBox.getWidth());
//    System.err.println("zoomBox height = " + zoomBox.getHeight());
    double longitude = zoomBox.getBottomLeftCoordinate().getLongitude() + (zoomBox.getWidth() * mouseEvent.x / mapCanvas.getSize().x);
    double latitude = zoomBox.getTopRightCoordinate().getLatitude() - (zoomBox.getHeight() * mouseEvent.y / mapCanvas.getSize().y);
//    System.err.println("lat = " + latitude);
//    System.err.println("lon = " + longitude);
    return new Coordinate(latitude, longitude, 0);
  }

  private void drawFeature(Ganesa ganesa, Feature feature, boolean isSelected, GC gc) {
    if (feature instanceof Point) {
      drawPoint(ganesa, (Point) feature, isSelected, gc);
    } else if (feature instanceof Arc) {
      drawArc(ganesa, (Arc) feature, isSelected, gc);
    } else if (feature instanceof Node) {
        drawPoint(ganesa, (Point) feature, isSelected, gc);
    } else {
      
    }
  }

  private double getRatio(Ganesa ganesa) {
    MercatorWithNorthingOffset mercator = new MercatorWithNorthingOffset();
    mercator.setOrigin(ganesa.getApplication().getSelectedProject().getZoomBox().getBottomLeftCoordinate());
    return mapCanvas.getClientArea().width / ganesa.getApplication().getSelectedProject().getZoomBox().getTopRightCoordinate().toProjected2DCoordinate(mercator).getEasting();
  }

  private void drawPoint(Ganesa ganesa, Point point, boolean isSelected, GC gc) {
    gc.setForeground(mapCanvas.getDisplay().getSystemColor(isSelected ? SWT.COLOR_YELLOW : SWT.COLOR_RED));
    gc.setBackground(mapCanvas.getDisplay().getSystemColor(isSelected ? SWT.COLOR_YELLOW : SWT.COLOR_RED));
    double ratio = getRatio(ganesa);

    MercatorWithNorthingOffset mercator = new MercatorWithNorthingOffset();
    mercator.setOrigin(ganesa.getApplication().getSelectedProject().getZoomBox().getBottomLeftCoordinate());
    Projected2DCoordinate projected2DCoordinate = point.getCoordinate().toProjected2DCoordinate(mercator);
    int x = new Double(projected2DCoordinate.getEasting() * ratio).intValue();
    int y = mapCanvas.getSize().y - new Double(projected2DCoordinate.getNorthing() * ratio).intValue();

    //gc.drawPoint(x, y);
    gc.drawOval(x, y, 1, 1);
  }

  private void drawArc(Ganesa ganesa, Arc way, boolean isSelected, GC gc) {
    MercatorWithNorthingOffset mercator = new MercatorWithNorthingOffset();
    mercator.setOrigin(ganesa.getApplication().getSelectedProject().getZoomBox().getBottomLeftCoordinate());

    gc.setForeground(mapCanvas.getDisplay().getSystemColor(isSelected ? SWT.COLOR_YELLOW : SWT.COLOR_BLACK));
    double ratio = getRatio(ganesa);
    Node prevNode = null;
    for (Node node : way.getVertices()) {
      if (prevNode != null) {
        int fromX = new Double(prevNode.getCoordinate().toProjected2DCoordinate(mercator).getEasting() * ratio).intValue();
        int fromY = mapCanvas.getSize().y - new Double(prevNode.getCoordinate().toProjected2DCoordinate(mercator).getNorthing() * ratio).intValue();
        int toX = new Double(node.getCoordinate().toProjected2DCoordinate(mercator).getEasting() * ratio).intValue();
        int toY = mapCanvas.getSize().y - new Double(node.getCoordinate().toProjected2DCoordinate(mercator).getNorthing() * ratio).intValue();
        gc.drawLine(fromX, fromY, toX, toY);
        //gc.drawText(way.getAttribute("name") + "", fromX + ((toX - fromX) / 2), fromY + ((toY - fromY) / 2), true);
      }
      prevNode = node;
    }
  }

  private void drawBoundingBox(Ganesa ganesa, Cuboid boundingBox, boolean isSelected, GC gc) {
    MercatorWithNorthingOffset mercator = new MercatorWithNorthingOffset();
    mercator.setOrigin(ganesa.getApplication().getSelectedProject().getZoomBox().getBottomLeftCoordinate());

    gc.setForeground(mapCanvas.getDisplay().getSystemColor(isSelected ? SWT.COLOR_YELLOW : SWT.COLOR_BLACK));
    double ratio = getRatio(ganesa);
    int fromX = new Double(boundingBox.getBottomLeftCoordinate().toProjected2DCoordinate(mercator).getEasting() * ratio).intValue();
    int fromY = mapCanvas.getSize().y - new Double(boundingBox.getBottomLeftCoordinate().toProjected2DCoordinate(mercator).getNorthing() * ratio).intValue();
    int toX = new Double(boundingBox.getTopRightCoordinate().toProjected2DCoordinate(mercator).getEasting() * ratio).intValue();
    int toY = mapCanvas.getSize().y - new Double(boundingBox.getTopRightCoordinate().toProjected2DCoordinate(mercator).getNorthing() * ratio).intValue();
    gc.drawLine(fromX, fromY, fromX, toY);
    gc.drawLine(fromX, toY, toX, toY);
    gc.drawLine(toX, toY, toX, fromY);
    gc.drawLine(toX, fromY, fromX, fromY);
  }

}
